﻿using Nancy.Security;
using Nancy;
using Nancy.ModelBinding;
using Famofo.AgbaraVOIP.Rest.Util;
using Famofo.AgbaraVOIP.Rest.FreeSwitch;
using Famofo.AgbaraVOIP.Rest.Model;
using Famofo.AgbaraVOIP.Core.FreeSwitch;
using System.Collections.Generic;
using System;

namespace Famofo.AgbaraVOIP.Rest.FreeSwitch
{
    public class RestApi :NancyModule
    {
        internal RESTInboundSocket inboundsocket;
        public RestApi()
            : base("v1.0")
        {

            Post["/Call"] = x =>
                {
                    PostCallRequest request = this.Bind();
                    PostCallResponse res = Call(request);
                    return Response.AsJson<PostCallResponse>(res);
                };
            Post["/BulkCall"] = x =>
            {
                PostBulkCallRequest request = this.Bind();
                PostBulkCallResponse res = BulkCall(request);
                return Response.AsJson<PostBulkCallResponse>(res);
            };
            Post["/HangupCall"] = x =>
            {
                PostHangUpRequest request = this.Bind();
                PostHangUpResponse res = HangUpCall(request);
                return Response.AsJson<PostHangUpResponse>(res);
            };
            Post["/TransferCall"] = x =>
            {
                PostTransferRequest request = this.Bind();
                PostTransferResponse res = TransferCall(request);
                return Response.AsJson<PostTransferResponse>(res);
            };
            Post["/HangupAllCalls"] = x =>
            {
                PostHangUpAllCallRequest request = this.Bind();
                PostHangUpAllResponse res = HangUpAllCall(request);
                return Response.AsJson<PostHangUpAllResponse>(res);
            };
            Post["/ScheduleHangup"] = x =>
            {
                PostScheduleHangUpRequest request = this.Bind();
                PostScheduleHangUpResponse res = ScheduleHangUpCall(request);
                return Response.AsXml<PostScheduleHangUpResponse>(res);
            };
            Post["/CancelScheduledHangup"] = x =>
            {
                PostCancelScheduledHangUpRequest request = this.Bind();
                PostCancelScheduledHangUpResponse res = CancelScheduleHangUpCall(request);
                return Response.AsXml<PostCancelScheduledHangUpResponse>(res);
            };
            Post["/SMS"] = x =>
            {
                PostCancelScheduledHangUpRequest request = this.Bind();
                PostCancelScheduledHangUpResponse res = CancelScheduleHangUpCall(request);
                return Response.AsXml<PostCancelScheduledHangUpResponse>(res);
            };
            Post["/Email"] = x =>
            {
                PostCancelScheduledHangUpRequest request = this.Bind();
                PostCancelScheduledHangUpResponse res = CancelScheduleHangUpCall(request);
                return Response.AsXml<PostCancelScheduledHangUpResponse>(res);
            };

        }
        private CallRequest _prepare_call_request(string caller_id, string to, string extra_dial_string, string gw, string gw_codecs,
                        string gw_timeouts, string gw_retries, string send_digits, string time_limit,
                         string hangup_on_ring, string answer_url, string ring_url, string hangup_url)
        {
            Stack<Gateway> gateways = new Stack<Gateway>();
            Queue<string> gw_retry_list = new Queue<string>();
            Queue<string> gw_codec_list = new Queue<string>();
            Queue<string> gw_timeout_list = new Queue<string>();
            Queue<string> gw_list = new Queue<string>();
            System.Text.StringBuilder args_list = new System.Text.StringBuilder();
            string sched_hangup_id = string.Empty;
            // don't allow "|" and "," in 'to' (destination) to avoid call injection
            to = to.Split(',', '|')[0];
            //# build gateways list removing trailing '/' character
            foreach (string gateway in gw.Split(','))
            {
                gw_list.Enqueue(gateway.TrimEnd('/'));
            }
            // split gw codecs by , but only outside the ''
            if (!string.IsNullOrEmpty(gw_codecs))
            {
                foreach (string g in gw_codecs.Split(','))
                {
                    gw_codec_list.Enqueue(g); //,(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''',gw_codecs)
                }
            }
            if (!string.IsNullOrEmpty(gw_timeouts))
            {
                foreach (string g in gw_timeouts.Split(','))
                {
                    gw_timeout_list.Enqueue(g);
                }
            }
            if (!string.IsNullOrEmpty(gw_retries))
            {
                foreach (string g in gw_retries.Split(','))
                {
                    gw_retry_list.Enqueue(g);
                }
            }
            // create a new request uuid
            string request_uuid = System.Guid.NewGuid().ToString();
            // append args
            args_list.Append(string.Format("agbaravoip_request_uuid={0}", request_uuid));
            args_list.Append(string.Format("agbaravoip_answer_url={0}", answer_url));
            args_list.Append(string.Format("origination_caller_id_number={0}", caller_id));

            // set extra_dial_string
            if (!string.IsNullOrEmpty(extra_dial_string))
                args_list.Append(extra_dial_string);

            //# set hangup_on_ring
            int hangup = 0;
            if (!int.TryParse(hangup_on_ring, out hangup))
                hangup = -1;
            if (hangup == 0)
                args_list.Append("execute_on_ring='hangup ORIGINATOR_CANCEL'");
            else if (hangup > 0)
                args_list.Append(string.Format("execute_on_ring='sched_hangup +{0} ORIGINATOR_CANCEL'", hangup));

            // set send_digits
            if (!string.IsNullOrEmpty(send_digits))
                args_list.Append(string.Format("execute_on_answer='send_dtmf {0}'", send_digits));

            //set time_limit
            int time = 0;
            if (!int.TryParse(time_limit, out time))
                time = -1;

            if (time > 0)
            {
                //create sched_hangup_id
                sched_hangup_id = System.Guid.NewGuid().ToString();
                args_list.Append(string.Format("api_on_answer='sched_api {0} +{1} hupall ALLOTTED_TIMEOUT agbaravoip_request_uuid {2}'", sched_hangup_id, time_limit, request_uuid));
                args_list.Append(string.Format("agbaravoip_sched_hangup_id={0}", sched_hangup_id));
            }
            // build originate string
            string args_str = string.Join(",", args_list);

            foreach (string gate in gw_list)
            {
                string codecs = string.Empty;
                int retry = 0;
                int timeout = 0;
                try
                {
                    codecs = gw_codec_list.Dequeue();
                }
                catch (Exception ex)
                {
                    codecs = "";
                }
                try
                {
                    retry = int.Parse(gw_retry_list.Dequeue());
                }
                catch (Exception ex)
                {
                    retry = 1;
                }
                try
                {
                    timeout = int.Parse(gw_timeout_list.Dequeue());
                }
                catch (Exception ex)
                {
                    timeout = 60;
                }
                for (int i = 1; i < retry; i++)
                {
                    Gateway gateway = new Gateway(request_uuid, to, gate, codecs, time.ToString(), extra_dial_string);
                    gateways.Push(gateway);
                }
            }

            CallRequest call_req = new CallRequest(request_uuid, gateways, answer_url, ring_url, hangup_url);
            return call_req;
        }
        private bool is_valid_url(string url)
        {
            return true;
        }
        private PostCallResponse Call(PostCallRequest request)
        {
            CallRequest call_req;
            #region comment

            /*Make Outbound Call
        Allow initiating outbound calls via the REST API. To make an
        outbound call, make an HTTP POST request to the resource URI.

        POST Parameters
        ----------------

        Required Parameters - You must POST the following parameters:

        From: The phone number to use as the caller id for the call without
        the leading +

        To: The number to call without the leading +

        Gateways: Comma separated string of gateways to dial the call out

        GatewayCodecs: Comma separated string of codecs for gateways

        GatewayTimeouts: Comma separated string of timeouts for gateways

        GatewayRetries: Comma separated string of retries for gateways

        AnswerUrl: The URL that should be requested for XML when the call
        connects

        TimeLimit: Define the max time of the call

        Optional Parameters - You may POST the following parameters:

        [HangUpUrl]: URL that Plivo will notify to, with POST params when
        calls ends

        [RingUrl]: URL that Plivo will notify to, with POST params when
        calls starts ringing

        [HangupOnRing]: If Set to 0 we will hangup as soon as the number ring,
        if set to value X we will wait X seconds when start ringing and then
        hang up

        [OriginateDialString]: Additional Originate dialstring to be executed
        while making the outbound call

        [SendDigits]: A string of keys to dial after connecting to the number.
        Valid digits in the string include: any digit (0-9), '#' and '*'.
        Very useful, if you want to connect to a company phone number,
        and wanted to dial extension 1234 and then the pound key,
        use SendDigits=1234#.
        Remember to URL-encode this string, since the '#' character has
        special meaning in a URL.
        To wait before sending DTMF to the extension, you can add leading 'w'
        or 'W' characters. Each 'w' character waits 0.5 seconds and each 'W'
        character waits for 1.0 seconds instead of sending a digit.
        */
            #endregion
            PostCallResponse response = new PostCallResponse();

            if (!string.IsNullOrEmpty(request.From) || !string.IsNullOrEmpty(request.To) || !string.IsNullOrEmpty(request.Gateways) || !string.IsNullOrEmpty(request.AnswerUrl))
            {
                response.Message = "Mandatory Parameters Missing";
                response.Result = false;
                response.RequestUUID = "";
                return response;
            }
            else if (is_valid_url(request.AnswerUrl))
            {
                response.Message = "Answer URL is not Valid";
                response.RequestUUID = "";
                response.Result = false;
                return response;
            }
            else
            {

                if (is_valid_url(request.HangupUrl))
                {
                    response.Message += "Hangup URL is not Valid";
                }
                else if (is_valid_url(request.RingUrl))
                {
                    response.Message += "Ring URL is not Valid";

                }
                else
                {
                    call_req = _prepare_call_request(request.From, request.To, request.ExtraDialString, request.Gateways, request.GatewayCodecs,
                                                    request.GatewayTimeouts, request.GatewayRetries, request.SendDigits, request.TimeLimit,
                                                     request.HangupOnRing, request.AnswerUrl, request.RingUrl, request.HangupUrl);


                    string request_uuid = call_req.request_uuid;
                    inboundsocket.call_requests[request_uuid] = call_req;
                    Console.WriteLine("call connected successfully");
                    //if (inboundsocket.spawn_originate(request_uuid))
                    //{
                    //    response.Message = "Call Request Executed Successfully";
                    //    response.Result = true;
                    //}
                    response.Message = "Call Request Executed Successfully";
                    response.Result = true;
                }
            }

            return response;
        }
        private PostBulkCallResponse BulkCall(PostBulkCallRequest request)
        {
            #region Comment
            /*Make Bulk Outbound Calls in one request
        Allow initiating bulk outbound calls via the REST API. To make a
        bulk outbound call, make an HTTP POST request to the resource URI.

        POST Parameters
        ----------------

        Required Parameters - You must POST the following parameters:

        From: The phone number to use as the caller id for the call without
        the leading +

        To: The number to call without the leading +

        Gateways: Comma separated string of gateways to dial the call out

        GatewayCodecs: Comma separated string of codecs for gateways

        GatewayTimeouts: Comma separated string of timeouts for gateways

        GatewayRetries: Comma separated string of retries for gateways

        AnswerUrl: The URL that should be requested for XML when the call
        connects. Similar to the URL for your inbound calls

        TimeLimit: Define the max time of the calls

        Optional Parameters - You may POST the following parameters:

        [HangUpUrl]: URL that Plivo will notify to, with POST params when
        calls ends

        [RingUrl]: URL that Plivo will notify to, with POST params when
        calls starts ringing

        [HangupOnRing]: If Set to 0 we will hangup as soon as the number ring,
        if set to value X we will wait X seconds when start ringing and then
        hang up

        [OriginateDialString]: Additional Originate dialstring to be executed
        while making the outbound call

        [SendDigits]: A string of keys to dial after connecting to the number.
        Valid digits in the string include: any digit (0-9), '#' and '*'.
        Very useful, if you want to connect to a company phone number,
        and wanted to dial extension 1234 and then the pound key,
        use SendDigits=1234#.
        Remember to URL-encode this string, since the '#' character has
        special meaning in a URL.
        To wait before sending DTMF to the extension, you can add leading 'w'
        characters. Each 'w' character waits 0.5 seconds instead of sending a
        digit.
        */
            #endregion
            PostBulkCallResponse response = new PostBulkCallResponse();
        //    msg = ""
        //result = False
        //request_uuid = ""

        //request_uuid_list = []
        //i = 0
        //if delimiter in (',', '/'):
        //    msg = "This Delimiter not allowed"
        //elif not caller_id or not to_str or not gw_str or not answer_url or\
        //    not delimiter:
        //    msg = "Mandatory Parameters Missing"
        //elif not is_valid_url(answer_url):
        //    msg = "Answer URL is not Valid"
        //else:
        //    hangup_url = get_post_param(request, 'HangupUrl')
        //    ring_url = get_post_param(request, 'RingUrl')
        //    if hangup_url and not is_valid_url(hangup_url):
        //        msg = "Hangup URL is not Valid"
        //    elif ring_url and not is_valid_url(ring_url):
        //        msg = "Ring URL is not Valid"
        //    else:
        //        extra_dial_string = get_post_param(request,'ExtraDialString')
        //        # Is a string of strings
        //        gw_codecs_str = get_post_param(request, 'GatewayCodecs')
        //        gw_timeouts_str = get_post_param(request, 'GatewayTimeouts')
        //        gw_retries_str = get_post_param(request, 'GatewayRetries')
        //        send_digits_str = get_post_param(request, 'SendDigits')
        //        time_limit_str = get_post_param(request, 'TimeLimit')
        //        hangup_on_ring_str = get_post_param(request, 'HangupOnRing')

        //        to_str_list = to_str.split(delimiter)
        //        gw_str_list = gw_str.split(delimiter)
        //        gw_codecs_str_list = gw_codecs_str.split(delimiter)
        //        gw_timeouts_str_list = gw_timeouts_str.split(delimiter)
        //        gw_retries_str_list = gw_retries_str.split(delimiter)
        //        send_digits_list = send_digits_str.split(delimiter)
        //        time_limit_list = time_limit_str.split(delimiter)
        //        hangup_on_ring_list = hangup_on_ring_str.split(delimiter)

        //        if len(to_str_list) < 2:
        //            msg = "BulkCalls should be used for at least 2 numbers"
        //        elif len(to_str_list) != len(gw_str_list):
        //            msg = "'To' parameter length does not match 'GW' Length"
        //        else:
        //            for to in to_str_list:
        //                try:
        //                    gw_codecs = gw_codecs_str_list[i]
        //                except IndexError:
        //                    gw_codecs = ""
        //                try:
        //                    gw_timeouts = gw_timeouts_str_list[i]
        //                except IndexError:
        //                    gw_timeouts = ""
        //                try:
        //                    gw_retries = gw_retries_str_list[i]
        //                except IndexError:
        //                    gw_retries = ""
        //                try:
        //                    send_digits = send_digits_list[i]
        //                except IndexError:
        //                    send_digits = ""
        //                try:
        //                    time_limit = time_limit_list[i]
        //                except IndexError:
        //                    time_limit = ""
        //                try:
        //                    hangup_on_ring = hangup_on_ring_list[i]
        //                except IndexError:
        //                    hangup_on_ring = ""

        //                call_req = self._prepare_call_request(
        //                            caller_id, to, extra_dial_string,
        //                            gw_str_list[i], gw_codecs, gw_timeouts, gw_retries,
        //                            send_digits, time_limit, hangup_on_ring,
        //                            answer_url, ring_url, hangup_url)
        //                request_uuid = call_req.request_uuid
        //                request_uuid_list.append(request_uuid)
        //                self._rest_inbound_socket.call_requests[request_uuid] = call_req
        //                i += 1

        //            # now do the calls !
        //            if self._rest_inbound_socket.bulk_originate(request_uuid_list):
        //                msg = "BulkCalls Requests Executed"
        //                result = True
        //            else:
        //                msg = "BulkCalls Requests Failed"
        //                request_uuid_list = []

        //return flask.jsonify(Success=result, Message=msg,
        //                     RequestUUID=request_uuid_list)
            return response;
        }
        private PostHangUpResponse HangUpCall(PostHangUpRequest request)
        {
            PostHangUpResponse response = new PostHangUpResponse();
            return response;
        }
        private PostTransferResponse TransferCall(PostTransferRequest request)
        {
            PostTransferResponse response = new PostTransferResponse();

        /*Transfer Call
        Realtime call transfer allows you to interrupt an in-progress
        call and place it another scenario.

        To transfer a live call, you make an HTTP POST request to a
        resource URI.

        POST Parameters
        ---------------
        The following parameters are available for you to POST when transfering
        a phone call:

        CallUUID: Unique Call ID to which the action should occur to.

        URL: A valid URL that returns RESTXML. Plivo will immediately fetch
              the XML and continue the call as the new XML.
        */
        
        
        if (!string.IsNullOrEmpty(request.CallUUID))
        {
            response.Message = "CallUUID Parameter must be present";
            response.Result = false;           
        }
        else if(!string.IsNullOrEmpty(request.Url))
        {
            response.Message ="URL Parameter must be present";
            response.Result = false;    
        }
        else if(!is_valid_url(request.Url))
        {
            response.Message ="URL is not Valid";
            response.Result = false;
        }
        // "Call UUID must be present with URL"
        if (inboundsocket.transfer_call(request.Url,request.CallUUID))
        {
            response.Message ="Transfer Call Executed";
            response.Result = true;
        }
        else
        {
            response.Message ="Transfer Call Failed";
            response.Result = true;
        }

            return response;
        }
        private PostHangUpAllResponse HangUpAllCall(PostHangUpAllCallRequest request)
        {
            PostHangUpAllResponse response = new PostHangUpAllResponse();

            //Hangup All Live Calls in the system

            if (inboundsocket.hangup_all_calls())
            {
                response.Message = "All Calls Hungup Successfuly";
                response.Result = true;
            }
            else
            {
                response.Message = "Calla Hangup failed!";
                response.Result = false;
            }
        
        
            return response;
        }
        private PostScheduleHangUpResponse ScheduleHangUpCall(PostScheduleHangUpRequest request)
        {
            PostScheduleHangUpResponse response = new PostScheduleHangUpResponse();

                                                        #region comment
       /*Schedule Call Hangup
        Schedule an hangup on a call in the future.

        To schedule a hangup, you make an HTTP POST request to a
        resource URI.

        POST Parameters
        ---------------
        The following parameters are available for you to POST when transfering
        a phone call:

        CallUUID: Unique Call ID to which the action should occur to.

        Time: When hanging up call in seconds.


        Returns a scheduled task with id SchedHangupId that you can use to cancel hangup.
        */
#endregion
        

        if (!string.IsNullOrEmpty(request.CallUUID))
        {
            response.Message = "CallUUID Parameter must be present";
            response.Result = false;
        }
        else if (!string.IsNullOrEmpty(request.Time))
        {
            response.Message = "Time Parameter must be present";
        }
        else
        {
            try
            {
                int time = 0;
                if (int.TryParse(request.Time, out time))
                {
                    if (time <= 0)
                    {
                        response.Message = "Time Parameter must be > 0 !";
                        response.Result = false;
                    }
                    else
                    {
                        string sched_id = Guid.NewGuid().ToString();
                        APIResponse res = (APIResponse)inboundsocket.api(string.Format("sched_api {0} +{1} uuid_kill {2} ALLOTTED_TIMEOUT", sched_id, time, request.CallUUID));
                        if (res.IsSuccess())
                        {
                            response.Message = string.Format("Scheduled Hangup Done with SchedHangupId {0}", sched_id);
                            response.Result = true;
                            response.ScheduleId = sched_id;
                        }
                        else
                        {
                            response.Message = string.Format("Scheduled Hangup Failed: {0}", res.GetResponse());
                            response.Result = false;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                response.Message = "Invalid Time Parameter !";
                response.Result = false;
            }
            
        }
        return response;
        }
        private PostCancelScheduledHangUpResponse CancelScheduleHangUpCall(PostCancelScheduledHangUpRequest request)
        {
            PostCancelScheduledHangUpResponse response = new PostCancelScheduledHangUpResponse();
#region Comment
        /*Cancel a Scheduled Call Hangup
        Unschedule an hangup on a call.

        To unschedule a hangup, you make an HTTP POST request to a
        resource URI.

        POST Parameters
        ---------------
        The following parameters are available for you to POST when transfering
        a phone call:

        SchedHangupId: id of the scheduled hangup.
        */
#endregion
        if (!string.IsNullOrEmpty(request.ScheduleId))
        {
            response.Message = "Id Parameter must be present";
            response.Result = false;
        }

        else
        {
            APIResponse res = (APIResponse)inboundsocket.api(string.Format("sched_del {0}", request.ScheduleId));
            if (res.IsSuccess())
            {
                response.Message = "Scheduled Hangup Canceled";
                response.Result = true;
            }

            else
            {
                response.Message = string.Format("Scheduled Hangup Cancelation Failed: {0}", res.GetResponse());
                response.Result = false;
            }
        }
        return response;
        }
    }
}
